#!
#
# service.sh: Functions for dealing with services supervised by daemontools.
#  They modify or extract info from @prefix@/local/services 
#  respectively @prefix@/local/etc/services, either directly or 
#  trought "svc" & "svstat" (daemontools).
#
# $Id: service.sh.in 722 2008-10-02 03:40:28Z enki $
# -------------------------------------------------------------------------
test $lib_service_sh || {

# -------------------------------------------------------------------------
: ${prefix:="@prefix@"}
: ${exec_prefix:="@prefix@"}
: ${sysconfdir:="/etc"}
: ${libdir:="${exec_prefix}/lib"}
: ${shlibdir:="${libdir}/sh"}
: ${servicedir:="/service"}

# -------------------------------------------------------------------------
. $shlibdir/util.sh
. $shlibdir/std/log.sh
. $shlibdir/sys/proc.sh
. $shlibdir/data/text.sh
. $shlibdir/std/array.sh

service_RUNDIR="$servicedir"          # directory containing enabled services
service_svcdir="$sysconfdir"              # directory containing all services

service_interval=0.5                    # time to wait while polling for an event
service_wait=0.25                       # time to wait for process to start/stop
                                        # after calling "svc"

# UTILITY FUNCTIONS
# ================================================================================

# service_name <path>...
#
# convert from service paths to names
# -------------------------------------------------------------------------
service_name()
{
  local service

  for service
  do
    echo "${service#*/service/}"
  done
}

# service_paths <name>...
#
# convert from service names to paths
# -------------------------------------------------------------------------
service_path()
{
  local service

  for service in "${@%%:*}"
  do
    case $service in
      */*) echo "$service" ;;
        *) echo "$service_RUNDIR/$service" ;;
    esac
  done
}

# SERVICE CONFIGURATION FUNCTIONS
# ================================================================================

# service_pidfile <name>
#
# get the pid file specified in the configuration of a service
# -------------------------------------------------------------------------
service_pidfile()
{
  eval eval echo "\$${1}_PIDFILE"
}

# service_for_each <function> [arguments]
#
# execute a function/command for each service known to the configuration.
# will abort (and return 1) after the first call to a function fails.
# -------------------------------------------------------------------------
service_for_each()
{
  local service fn="$1"

  shift

  for service in $SERVICES
  do
    "$fn" "$service" "$@" || return 1
  done
}

# LOW-LEVEL SERVICE MANAGEMENT
# ================================================================================

# Checks if the specified service exists, which means it has a
# corresponding directory in @prefix@/local/etc/services
#
# Can be used in the form:
#
# service_exists <service>... && {
#   <actions>...
# }
# -------------------------------------------------------------------------
service_exists()
{
  local service svcdir="${2-$service_svcdir}"

  for service
  do
    if test ! -d "$svcdir/$service"
    then
      return 1
    fi
  done
}

# Like the one above, but yields an error message when a service doesn't exist
# -------------------------------------------------------------------------
service_mustexist()
{
  local service

  for service
  do
    if test ! -e "$service_svcdir/$service/run"
    then
      echo "$service: no such service"
      return 1
    fi
  done
}

# -------------------------------------------------------------------------
service_mustnotexist()
{
  local svcdir=${2-$service_svcdir}

  if test -e "$svcdir/$1/run"
  then
    msg "Service '$1' already exists."
    return 1
  fi

  return 0
}

# Checks if the specified service(s) is/are enabled, which means they are
# symlinked from @prefix@/local/services to @prefix@/local/etc/services.
#
# Can be used in the form:
#
# service_enabled <service>... && {
#   <actions>...
# }
# -------------------------------------------------------------------------
service_enabled()
{
  local service

  service_exists "$@" &&
  for service
  do
    if test ! -d $service_RUNDIR/$service
    then
      return 1
    fi
  done
}

# Like the one above, but yields an error message when a service isn't enabled
# or doesn't even exist
# -------------------------------------------------------------------------
service_mustbeenabled()
{
  local service

  service_mustexist "$@" &&
  for service
  do
    if test ! -d $service_RUNDIR/$service
    then
      echo "$service: disabled"
      return 1
    fi
  done
}

# service_enable <service>...
#
# Enable one or more services
# -------------------------------------------------------------------------
service_enable()
{
  service_mustexist "$@" &&
  for service
  do
    if ! service_enabled "$service"; then
      ln -s "$service_svcdir/$service" "$service_RUNDIR"
    else
      errormsg "${service}: already enabled"
    fi
  done
}

# service_disable <service>...
#
# Disable one or more services
# -------------------------------------------------------------------------
service_disable()
{
  service_mustexist "$@" &&
  for service
  do
    if service_enabled "$service"
    then
      rm -f "$service_RUNDIR/$service"
    else
      errormsg "$service: already disabled"
    fi
  done
}

# SERVICES MONITORING
# ================================================================================

# Get the service(s) status using "svstat" (daemontools)
#
# service_stat <service>...
#
# It will print out a line for each service like:
#
# @prefix@/local/service/httpd: up (pid 31737) 3556 seconds
# @prefix@/local/service/mysqld: down 1515 seconds, normally up
# -------------------------------------------------------------------------
service_stat()
{
  local service

#  service_mustbeenabled "$@" &&
  svstat `service_path "$@"` 2>/dev/null
}

# Get the process id of services using the daemontools backend
#
# service_getpid <name...|svstat-output...>
#
# You can optionally supply the output from a previous call to service_stat,
# so it won't call svstat by itself
# -------------------------------------------------------------------------
service_getpid()
{
  local pid service stat IFS="
"
  case $* in
    *:*) set -- $* ;;
      *) set -- `service_stat $*` ;;
  esac

  echo "$*" | sed -n -e "/^[^:]\+: up (/ s/.*pid \([0-9]*\).*/\1/ p"
}

#service_getpid_boot()
#{
#  pgrep -f 'svscanboot$'
#}

# Get the process id of a service using the configuration
#
# service_getpid_file <name>
# -------------------------------------------------------------------------
#service_getpid_file()
#{
#  local pidfile=`service_pidfile "$1"`
#
#  if test -f "$pidfile"
#  then
#    cat "$pidfile"
#  fi
#}

# service_gettime <name> [svstat]
#
# Get the time a particular service has been running for
# -------------------------------------------------------------------------
service_gettime()
{
  local time service=$1

  shift

  time=$(array ${@-`service_stat ${service}`} | sed -n '/^[0-9]\+$/ { N; p }')

  if test -n "$time"
  then
    echo "$time"
  fi
}

# service_getstatus <name> [svstat]
#
# get the status of a service (up, down, disabled)
# -------------------------------------------------------------------------
service_getstatus()
{
  local time service=$1

  shift

  status=$(echo ${@-`service_stat "$service"`} | \
           sed 's|.*supervise not running|failure|;;
                s|.*file does not exist|disabled|;;
                s|.*: ||;;
                s| (.*||;;
                s|.*down .*|down|;;
                s|.*up .*|up|')

  if test -n "$status"
  then
    echo "$status"
  fi
}

# service_supervised <name> [svstat]
#
# check if a service is supervised, which means that it must be enabled
# and svscanboot and the supervise process corresponding to that service
# must be running.
# -------------------------------------------------------------------------
service_supervised()
{
  local service=$1

  shift

  test "`service_getstatus ${service} "$@"`" != "disabled"
}

# service_mustbesupervised <name>
#
# same as above, but printing an error message
# -------------------------------------------------------------------------
service_mustbesupervised()
{
  proc_mustbeinstalled &&
  if ! service_supervised "$1"
  then
    errormsg "$1 is disabled"
    return 1
  fi
}

# SERVICES CONTROL   (duh, not very nice concept here)
# ================================================================================

# service_up <service>...
#
# Start a service
# -------------------------------------------------------------------------
service_up()
{
  service_mustnotrun "$@" &&
 (svc -u `service_path "$@"`)
}

# service_down <name>
#
# Stop a service
# -------------------------------------------------------------------------
service_down()
{
  service_mustrun "$@" &&
 (svc -d `service_path "$@"`)
}

# SERVICES API
# ================================================================================

# get human readable info about a service
#
# service_getinfo <service>...
# -------------------------------------------------------------------------
service_getinfo()
{
  local svstat=`service_stat "$1"`
  local status=`service_getstatus "$1" "$svstat"`
  local time=`service_gettime "$1" "$svstat"`

  case $status in
    up)
      local pid=`service_getpid "$1" "$svstat"`
      echo "up since ${time} (pid ${pid})"
      ;;
    down)
      echo "down since ${time}"
      ;;
    'file does not exist')
      echo "disabled"
      ;;
    *)
      echo "$status"
      ;;
   esac
}

# waiting for a service to come up
# -------------------------------------------------------------------------
service_wait_up()
{
  while sleep $service_interval
  do
    service_runs "$@" && break
  done
}

# waiting for a service to terminate
# -------------------------------------------------------------------------
service_wait_down()
{
  while sleep $service_interval
  do
    service_runs "$@" || break
  done
}

# service_mustnotrun <name>
# -------------------------------------------------------------------------
service_mustnotrun()
{
  if service_runs "$1"
  then
    errormsg "$1 already running"
    return 1
  fi
}

# service_runs <name> [svstat]
# -------------------------------------------------------------------------
service_runs()
{
#  echo $1 `service_getstatus "$@"`
  test "`service_getstatus "$@"`" = "up"
}

# service_mustrun <name>
# -------------------------------------------------------------------------
service_mustrun()
{
  if ! service_runs "$@"
  then
    errormsg "$1 not running"
    return 1
  fi
}

# SERVICES COMMAND LINE CLIENT
# ================================================================================

# service_create <name> <run> [svcdir]
# -------------------------------------------------------------------------
service_create()
{
  local svcdir="${3-$service_svcdir}"

  service_mustnotexist "$1" &&
  {
    mkdir -p "$svcdir/$1"

    if is_object "$2"
    then
      ln -s "$2" "$svcdir/$1/run"
    else
      cp -fL "$2" "$svcdir/$1/run"
    fi 2>/dev/null

    msg "Created new service '$1'."
    echo "$svcdir/$1"
  }
}

# service_setparams <name> [params...]
# -------------------------------------------------------------------------
service_setparams()
{
  local service="$1"

  shift

  service_mustexist "$service" &&
  {
    for param
    do
      echo "$param"
    done >$service_svcdir/$service/params
  }
}

# service_list [svcdir]
# -------------------------------------------------------------------------
service_list()
{
  local svcdir="${1-$service_svcdir}"

 (cd "$svcdir"

  for svc in */run
  do
    case $svc in
      supervise/run|*/supervise/run)
        ;;
      */run)
        if test -e "$svc" -a ! -d "$svc" -a -d "${svc%/run}"
        then
          echo ${svc%/run}
        fi
        ;;
    esac
  done)# 2>/dev/null
}

# service_tree [svcdir]
# -------------------------------------------------------------------------
service_tree()
{
  local svcdir="${1-$service_svcdir}"
  local pfx=${2-$svcdir/}

  (cd "$svcdir"

   for svc in */run
   do
     if test -e "$svc" && test "$svc" != "supervise/run"
     then
       svc=${svc%/run}
       echo "$pfx$svc"
       service_tree "$svcdir/$svc" ${2+"$2$svc/"}
     fi
   done) 2>/dev/null
}

# try to start a service and track its log while it is coming up
# -------------------------------------------------------------------------
service_start()
{
  service_mustbesupervised $1 &&
  service_mustnotrun $1 &&
  {
    local logpid stop

    msg "starting $1..."

    if var_isset "$1_START"
    then
      (log_follow --stop="`var_get "$1_START"`" --lines=0 --prefix $1) & logpid=$!
    fi

    service_up $1

    if ! var_isset "$1_START"
    then
      service_wait_up $1
    fi

    wait ${logpid} 2>/dev/null
  }
}

# try to stop a service and track its log while it is shutting down
# -------------------------------------------------------------------------
service_stop()
{
  service_mustbesupervised $1 &&
  service_mustrun $1 &&
  {
    local logpid stop

    msg "stopping $1 (`var_get "$1_STOP"`)..."

    if var_isset "$1_STOP"
    then
      (log_follow --stop="`var_get "$1_STOP"`" --lines=0 --prefix $1) & logpid=$!
    fi

    service_down $1

    if ! var_isset "$1_STOP"
    then
      service_wait_down $1
    fi

    wait $((logpid)) 2>/dev/null
  }
}

# service_wrap <function> [services...]
# -------------------------------------------------------------------------
service_wrap()
{
  local fn="$1" service services

  shift

  if test "$#" -gt 0
  then
    services="$@"
    for service in $services
    do
      service_mustexist $service || return 1
    done
  else
    services=$SERVICES
  fi

  for service in $services
  do
    $fn $service
  done
}

# show the status of a service
#
# service_status <service>
# -------------------------------------------------------------------------
service_status()
{
  info=`service_getinfo $1`

  # fancy colorful :)
#  info=`echo ${info} | sed 's,^up,\\033[32;1mup\\033[0m,;;s,^down,\\033[31;1mdown\\033[0m,;;s,^disabled,\\033[33;disabled\\033[0m,'`

  echo -e $1: ${info}
}

# add services to autostart list (means enable)
# -------------------------------------------------------------------------
service_add()
{
  if service_enabled "$@"
  then
    msg "service $@ already enabled"
  else
    if service_enable "$@"
    then
      msg "enabled service $@"
    else
      msg "failed enabling service $@"
    fi
  fi
}

# remove services from autostart list (means disable)
# -------------------------------------------------------------------------
service_remove()
{
  if service_runs "$@"
  then
    msg "service $@ still running"
    return 1
  fi

  if ! service_enabled "$@"
  then
    msg "service $@ already disabled"
  else
    if service_disable "$@"
    then
      msg "disabled service $@"
    else
      msg "failed disabling service $@"
    fi
  fi
}

# print a list of all services that are running
# -------------------------------------------------------------------------
service_running()
{
  local service

  for service in $SERVICES
  do
    if service_runs "$service"
    then
      echo "$service"
    fi
  done
}

# service_clean_rc <file> <trailing comments>
# -------------------------------------------------------------------------
service_clean_inittab()
{
  local inittab="`cat $1`"

  if test -n "$2"
  then
    echo "`echo "${inittab%"$2"}"`"
  else
    echo "$inittab"
  fi
}

# get trailing comments from inittab
#
# service_end_rc <file>
# -------------------------------------------------------------------------
service_end_inittab()
{
  local line end

  while read line
  do
    case $line in
      ""|" "|"  "|"   ")
        ;;
      "#"*)
        end="${end}
${line}"
        ;;
      *)
        end=""
        ;;
    esac
  done < $1

  echo "$end"
}

# determine the rcfile
service_rcfile()
{
  local end temp

  case $target in
    *freebsd*)
      rcfile='/etc/rc'
      ;;
    *)
      local rc

      for rc in '/etc/inittab' '/etc/rc'
      do
        if test -f "$rc"
        then
          rcfile="$rc"
          break
        fi
      done
      ;;
  esac

  case $rcfile in
    '/etc/inittab')
      entry_add="SV:12345:respawn:$prefix/bin/svscanboot"
      comment_add='# automatically added by nexsvc, do NOT change!'

      entry_search="SV.*svscanboot"
      comment_search='.*added by nex.*'

      end_of_search='# end of.*'
      ;;

    '/etc/rc')
      entry_add="@prefix@/local/bin/setsid.static @prefix@/local/bin/svscanboot &"
      comment_add='# automatically added by nexsvc, do NOT change!'

      entry_search=".*setsid.*svscanboot ."
      comment_search='.*added by nex.*'

      end_of_search='# end of.*'
      ;;
  esac
}

# install svscanboot to the inittab or rc-script
# -------------------------------------------------------------------------
service_install()
{
  local end temp

  service_rcfile

  # backup rcfile
  if test ! -f "$rcfile.backup" && test -s "$rcfile"
  then
    cp "$rcfile" "$rcfile.backup"
  fi

  if ! grep -q "^$entry_search$" $rcfile
  then
    case $target in
      *freebsd*)
        text_insert_before "$comment_add$newline$entry_add" "^echo''" $rcfile ||
        text_insert_before "$comment_add$newline$entry_add" "^date" $rcfile ||
        text_insert_before "$comment_add$newline$entry_add" "^exit 0" $rcfile ||
        text_append "$comment_add$newline$entry_add" $rcfile
        ;;
      *)
        if ! grep -q "^$end_of_search$" $rcfile; then
          text_append "$comment_add$newline$entry_add" $rcfile
        else
          text_insert_before "$comment_add$newline$entry_add" "^$end_of_search$" ${rcfile}
        fi
        ;;
    esac
    msg "added $prefix/bin/svscanboot to ${rcfile}"
    return 0
  else
    msg "svscanboot already in ${rcfile}"
    return 1
  fi
}

# remove svscanboot from the inittab
# -------------------------------------------------------------------------
service_uninstall()
{
  local temp tab

  service_rcfile

  if grep -q "^${entry_search}$" "$rcfile"
  then
    text_remove "^${entry_search}$" "$rcfile"
    text_remove "^${comment_search}$" "$rcfile"

    msg "removed svscanboot from ${rcfile}"

    return 0
  else
    msg "svscanboot not in ${rcfile}"
    return 1
  fi
}

service_mustbeinstalled()
{
  if ! grep -q "^${entry_search}$" "$rcfile"
  then
    msg "svscanboot not in ${rcfile}, use nexsvc --install"
    return 1
  fi

  return 0
}

service_isinstalled()
{
  grep -q "^${entry_search}$" "$rcfile"
}

# rehash init [1]
# -------------------------------------------------------------------------
service_rehash()
{
  service_rcfile

  case $rcfile in
    '/etc/inittab')
      msg "sending SIGHUP to init [1]"
      kill -HUP 1
      ;;
    '/etc/rc')
      local pid=`proc_svscanboot`

      if test -n "$pid"
      then
        if ! service_isinstalled
        then
          msg "sending SIGTERM to svscanboot [$pid]"
          kill -TERM "$pid"
        else
          msg "svscanboot is already running [$pid]"
        fi
      else
        service_mustbeinstalled &&
        {
          setsid @prefix@/local/bin/svscanboot &
          msg "launched svscanboot [$!]"
        }
      fi
      ;;
  esac
}

# shut down everything (before uninstall)
# -------------------------------------------------------------------------
service_fullstop()
{
  local services svc_pids file_pids tree_pids all_pids

  # get a list of running services and their pids
  services=`service_running`
  svc_pids=`service_for_each service_getpid_svc`

  # get list of running processes by their pid files
  file_pids=`service_for_each service_getpid_file`

  # get list of running processes by the more direct
  # approach of searching svscanboot in the process list
  # and then tracking all its children
  tree_pids=`proc_supervised`

  # merge all pids
  set -- `proc_merge ${svc_pids} ${file_pids} ${tree_pids}`

  if test -n "$services"
  then
    msg "$# processes in `proc_count ${services}` services to consider"

    # terminate services using the conventional method
    for_each service_down ${services}
  else
    msg "$# processes to consider"
  fi

  # remove svscan from rcfile and rehash init
  service_uninstall
  service_rehash

  # force those who didn't terminate
  local still_running=`proc_still_running $svc_pids`

  for pid in $still_running
  do
    name=`proc_cmdline $pid`
    msg "$name ($pid) didn't terminate properly, killing it..."

    kill $pid
  done

  if test -n "$still_running"
  then
    msg "waiting a moment to terminate..."
    sleep 1
  fi

  # get those which are still running
  set -- `proc_still_running "$@"`

  if test "$#" -gt 0
  then
    msg "$# processes still running, killing them now..."

    kill "$@" 2>/dev/null

    msg "waiting a moment to terminate..."
    sleep 1

    set -- `proc_still_running "$@"`

    if test "$#" -gt 0
    then
      msg "$# processes STILL running (!!!)"
    fi
  else
    msg "clean shutdown"
  fi
}

# show all pids
service_pids()
{
  local services svc_pids file_pids tree_pids all_pids

  # get a list of running services and their pids
  services=`service_running`
  svc_pids=`service_for_each service_getpid_svc`

  # get list of running processes by their pid files
  file_pids=`service_for_each service_getpid_file`

  # get list of running processes by the more direct
  # approach of searching svscanboot in the process list
  # and then tracking all its children
  tree_pids=`proc_supervised`

  # merge all pids
  set -- `proc_merge ${svc_pids} ${file_pids} ${tree_pids}`

  echo "$@"
}

# SERVICES API FROM WITHIN RUN SCRIPT
# ================================================================================
service_msg()
{
  echo "$@" 1>&2
}

service_error()
{
  service_msg "$@"
  exec sleep $((15 * 60))
}

# --- eof ---------------------------------------------------------------------
lib_service_sh=:;}
